﻿/*******************************************************************************************************************************************
Modification History:
********************************************************************************************************************************************
Date		Author		    Description
********************************************************************************************************************************************
05/08/2011	Brian Kuhn	    Created Specification<T> class
*******************************************************************************************************************************************/
using System;
using System.Linq;
using System.Linq.Expressions;

namespace Concise
{
    /// <summary>
    /// Represents a generic specification that defines a set of criteria.
    /// </summary>
    /// <typeparam name="T">The type the specification defines a set of criteria for.</typeparam>
    /// <remarks>
    ///  The <see cref="Specification{T}"/> class implements the <i>Composite Specification</i> pattern by overloading
    /// the logical AND and locagical OR operators to allow composition of individual specifications.
    /// </remarks>
    public class Specification<T> : ISpecification<T>, IEquatable<ISpecification<T>>
    {
        //=======================================================================================================
        //	Constructors
        //=======================================================================================================
        #region Specification(Expression<Func<T, bool>> predicate)
        /// <summary>
        /// Initializes a new instance of the <see cref="Specification{T}"/> class 
        /// using the specified predicate.
        /// </summary>
        /// <param name="predicate">The predicate used to determine if entities satisfy the conditions of the specification.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="predicate"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        public Specification(Expression<Func<T, bool>> predicate)
        {
            this.Predicate = predicate;
        }
        #endregion

        //=======================================================================================================
        //	Public Methods
        //=======================================================================================================
        #region BitwiseAnd(Specification<T> left, Specification<T> right)
        /// <summary>
        /// Combines two <see cref="Specification{T}"/> in a Boolean <b>And</b> expression 
        /// as a new <see cref="Specification{T}"/>.
        /// </summary>
        /// <param name="left">The left hand <see cref="Specification{T}"/> to combine.</param>
        /// <param name="right">The right hand <see cref="Specification{T}"/> to combine.</param>
        /// <returns>The combined <see cref="Specification{T}"/> instance.</returns>
        public Specification<T> BitwiseAnd(Specification<T> left, Specification<T> right)
        {
            return left & right;
        }
        #endregion

        #region BitwiseOr(Specification<T> left, Specification<T> right)
        /// <summary>
        /// Combines two <see cref="Specification{T}"/> 
        /// in a Boolean <b>Or</b> expression as a new <see cref="Specification{T}"/>.
        /// </summary>
        /// <param name="left">The left hand <see cref="Specification{T}"/> to combine.</param>
        /// <param name="right">The right hand <see cref="Specification{T}"/> to combine.</param>
        /// <returns>The combined <see cref="Specification{T}"/> instance.</returns>
        public Specification<T> BitwiseOr(Specification<T> left, Specification<T> right)
        {
            return left | right;
        }
        #endregion

        //=======================================================================================================
        //	Operator Overloads
        //=======================================================================================================
        #region &(Specification<T> left, Specification<T> right)
        /// <summary>
        /// Overloads the &amp; operator in order to combine two <see cref="Specification{T}"/> 
        /// in a Boolean <b>And</b> expression as a new <see cref="Specification{T}"/>.
        /// </summary>
        /// <param name="left">The left hand <see cref="Specification{T}"/> to combine.</param>
        /// <param name="right">The right hand <see cref="Specification{T}"/> to combine.</param>
        /// <returns>The combined <see cref="Specification{T}"/> instance.</returns>
        public static Specification<T> operator &(Specification<T> left, Specification<T> right)
        {
            InvocationExpression rightInvoke    = Expression.Invoke(right.Predicate, left.Predicate.Parameters.Cast<Expression>());
            BinaryExpression binaryExpression   = Expression.MakeBinary(ExpressionType.AndAlso, left.Predicate.Body, rightInvoke);

            return new Specification<T>(Expression.Lambda<Func<T, bool>>(binaryExpression, left.Predicate.Parameters));
        }
        #endregion

        #region |(Specification<T> left, Specification<T> right)
        /// <summary>
        /// Overloads the | operator in order to combine two <see cref="Specification{T}"/> 
        /// in a Boolean <b>Or</b> expression as a new <see cref="Specification{T}"/>.
        /// </summary>
        /// <param name="left">The left hand <see cref="Specification{T}"/> to combine.</param>
        /// <param name="right">The right hand <see cref="Specification{T}"/> to combine.</param>
        /// <returns>The combined <see cref="Specification{T}"/> instance.</returns>
        public static Specification<T> operator |(Specification<T> left, Specification<T> right)
        {
            InvocationExpression rightInvoke    = Expression.Invoke(right.Predicate, left.Predicate.Parameters.Cast<Expression>());
            BinaryExpression binaryExpression   = Expression.MakeBinary(ExpressionType.OrElse, left.Predicate.Body, rightInvoke);

            return new Specification<T>(Expression.Lambda<Func<T, bool>>(binaryExpression, left.Predicate.Parameters));
        }
        #endregion

        //=======================================================================================================
        //	ISpecification Implementation
        //=======================================================================================================
        #region IsSatisfiedBy(object entity)
        /// <summary>
        /// Evaluates this specification against the specified entity.
        /// </summary>
        /// <param name="entity">The instance to evaluate this specificaton against.</param>
        /// <returns>
        /// <see langword="true" /> if this specification was satisfied by the entity; otherwise, <see langword="false" />.
        /// </returns>
        public bool IsSatisfiedBy(object entity)
        {
            T target    = default(T);

            try
            {
                target  = (T)entity;
            }
            catch (InvalidCastException)
            {
                target  = default(T);
            }

            return _specificationPredicateDelegate.Invoke(target);
        }
        private Func<T, bool> _specificationPredicateDelegate;
        #endregion

        //=======================================================================================================
        //  IEquatable<ISpecification<T>> Implementation
        //=======================================================================================================
        #region Equals(ISpecification<T> other)
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns><see langword="true"/> if the current object is equal to the other parameter; otherwise, <see langword="false"/>.</returns>
        public bool Equals(ISpecification<T> other)
        {
            if (other == null)
            {
                return false;
            }

            return Expression.Equals(this.Predicate, other.Predicate);
        }
        #endregion

        #region Equals(object obj)
        /// <summary>
        /// Determines whether the specified <see cref="Object"/> is equal to the current <see cref="Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="Object"/> to compare with the current <see cref="Object"/>.</param>
        /// <returns>
        /// <see langword="true"/> if the specified <see cref="Object"/> is equal to the current <see cref="Object"/>; otherwise, <see langword="false"/>.
        /// </returns>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as ISpecification<T>);
        }
        #endregion

        #region Equals(ISpecification<T> first, ISpecification<T> second)
        /// <summary>
        /// Determines whether two specified <see cref="ISpecification{T}"/> objects have the same value.
        /// </summary>
        /// <param name="first">The first object to compare, or <see langword="null"/>.</param>
        /// <param name="second">The second object to compare, or <see langword="null"/>.</param>
        /// <returns>
        /// <see langword="true"/> if the value of <paramref name="first"/> object is the same as the value of the <paramref name="second"/> object; otherwise, <see langword="false"/>.
        /// </returns>
        public static bool Equals(ISpecification<T> first, ISpecification<T> second)
        {
            if (first == null && second != null)
            {
                return false;
            }

            if(first != null && second == null)
            {
                return false;
            }

            if (first == null && second == null)
            {
                return true;
            }

            return first.Equals(second);
        }
        #endregion

        #region GetHashCode()
        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        /// <a href="http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx"/>
        public override int GetHashCode()
        {
            int predicateHashCode   = this.Predicate != null ? this.Predicate.GetHashCode() : 0;

            /*
             * The 23 and 37 are arbitrary numbers which are co-prime.
             * 
             * The benefit of the below over the XOR (^) method is that if you have a type 
             * which has two values which are frequently the same, XORing those values 
             * will always give the same result (0) whereas the above will 
             * differentiate between them unless you're very unlucky.
            */
            int hashCode    = 23;
            hashCode        = hashCode * 37 + predicateHashCode;
            
            return hashCode;
        }
        #endregion

        //=======================================================================================================
        //	ISpecification<T> Implementation
        //=======================================================================================================
        #region Predicate
        /// <summary>
        /// Gets or sets the expression that defines the criteria of this specification.
        /// </summary>
        /// <value>
        /// A <see cref="Expression{TDelegate}"/> that defines the criteria of this specification.
        /// </value>
        /// <exception cref="ArgumentNullException">The <paramref name="value"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        public Expression<Func<T, bool>> Predicate
        {
            get
            {
                return _specificationPredicate;
            }

            set
            {
                Guard.AgainstNullReference(value, "value");
                
                _specificationPredicate         = value;
                _specificationPredicateDelegate = _specificationPredicate.Compile();
            }
        }
        private Expression<Func<T, bool>> _specificationPredicate;
        #endregion

        #region IsSatisfiedBy(T entity)
        /// <summary>
        /// Evaluates this specification against the specified entity.
        /// </summary>
        /// <param name="entity">The instance of type <typeparamref name="T"/> to evaluate this specificaton against.</param>
        /// <returns>
        /// <see langword="true" /> if this specification was satisfied by the entity; otherwise, <see langword="false" />.
        /// </returns>
        public bool IsSatisfiedBy(T entity)
        {
            return _specificationPredicateDelegate.Invoke(entity);
        }
        #endregion
    }
}
